/*******************************************************************************
 * Copyright (c) 2015 THALES GLOBAL SERVICES.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Obeo - initial API and implementation
 *******************************************************************************/
package org.yakindu.base.xtext.utils.gmf.routing;

import java.util.List;
import java.util.Optional;

import org.eclipse.draw2d.ConnectionLocator;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Straight;
import org.eclipse.draw2d.geometry.Transform;
import org.eclipse.draw2d.geometry.Vector;
import org.eclipse.gmf.runtime.diagram.ui.editparts.LabelEditPart;
import org.eclipse.gmf.runtime.diagram.ui.internal.util.LabelViewConstants;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.PointListUtilities;
import org.eclipse.gmf.runtime.notation.Bounds;

/**
 * Utility class used to compute the position of a label according to its edge
 * move (old points and new points list).
 *
 * @author <a href="mailto:laurent.fasani@obeo.fr">Laurent Fasani</a>
 * @author <a href="mailto:laurent.redor@obeo.fr">Laurent Redor</a>
 */
@SuppressWarnings("restriction")
public class EdgeLabelQuery {

	private final static double DISTANCE_TOLERANCE = 0.001;

	/** Status for {@link #getSameLineStatus(Vector, LineSeg)} method. */
	private static final int NOT_ON_SAME_LINE = 0;

	/** Status for {@link #getSameLineStatus(Vector, LineSeg)} method. */
	private static final int ON_SAME_LINE_SAME_DIRECTION = 1;

	/** Status for {@link #getSameLineStatus(Vector, LineSeg)} method. */
	private static final int ON_SAME_LINE_OPPOSITE_DIRECTION = 2;

	/** BendPoint list before the edge modification. */
	private PointList oldBendPointList;

	/** BendPoint list corresponding to the edge modification. */
	private PointList newBendPointList;

	/** The routing status of edge on which the label is. */
	private boolean isEdgeWithObliqueRoutingStyle;

	/** The old offset of the label */
	private Point oldLabelOffset;

	/**
	 * The keyPoint of the label (
	 * {@link org.eclipse.gmf.runtime.diagram.ui.editparts.LabelEditPart#getKeyPoint()}
	 * ).
	 */
	private Integer keyPoint;

	private List<LineSeg> oldEdgeSegments;

	private List<LineSeg> newEdgeSegments;

	/**
	 * True if the parent's label is a bracketEdge (specific locator for center
	 * label and possibility to rotate the middle segment), false otherwise.
	 */
	private boolean isOnBracketEdge;

	/** The size of the label. */
	private Dimension labelSize;

	/**
	 * Return the default snap back position according to the keyPoint of the label.
	 *
	 * @param keyPoint
	 *            The keyPoint of the label (
	 *            {@link org.eclipse.gmf.runtime.diagram.ui.editparts.LabelEditPart#getKeyPoint()}
	 *            ).
	 * @return the default snap back position according to the keyPoint.
	 */
	public static Point getSnapBackPosition(Integer keyPoint) {
		return new Point();
	}

	/**
	 * Calculates the label offset from the reference point given the label center
	 * and a points list.<BR>
	 * {@link org.eclipse.gmf.runtime.diagram.ui.internal.figures.LabelHelper#offsetFromRelativeCoordinate(org.eclipse.draw2d.IFigure, org.eclipse.draw2d.geometry.Rectangle, Point)}
	 *
	 * @param labelCenter
	 *            the label center for the <code>IFigure</code> to calculate the
	 *            offset for
	 * @param points
	 *            the <code>PointList</code> of the edge that contains the label.
	 *            The label offset is relative to this <code>PointList</code>.
	 * @param ref
	 *            the <code>Point</code> that is the reference point that the offset
	 *            is based on.
	 * @return a <code>Point</code> which represents a value offset from the
	 *         <code>ref</code> point oriented based on the nearest line segment.
	 */
	public static Point offsetFromRelativeCoordinate(Point labelCenter, PointList points, Point ref) {
		Vector fromAnchorToLabelCenterPointVector = new Vector(labelCenter.x - ref.x, labelCenter.y - ref.y);
		@SuppressWarnings("rawtypes")
		List lineSegments = PointListUtilities.getLineSegments(points);
		LineSeg segmentContainingLabelAnchor = PointListUtilities.getNearestSegment(lineSegments, ref.x, ref.y);
		Vector rotatedVector = getRotatedVector(fromAnchorToLabelCenterPointVector, segmentContainingLabelAnchor,
				false);

		return new PrecisionPoint(rotatedVector.x, rotatedVector.y);
	}

	/**
	 * Calculates the relative coordinate that is equivalent to the offset from the
	 * reference point, that can be used to set the label center location. <BR>
	 *
	 * This is another implementation of
	 * {@link org.eclipse.gmf.runtime.diagram.ui.internal.figures.LabelHelper#relativeCoordinateFromOffset(org.eclipse.draw2d.IFigure, Point, Point)}
	 * . See bugzilla 476305 for more detail.
	 *
	 * @param points
	 *            the <code>PointList</code> of the edge that contains the label.
	 *            The label offset is relative to this <code>PointList</code>.
	 * @param ref
	 *            a <code>Point</code> located on the parent which the offset value
	 *            is relative to.
	 * @param offset
	 *            a <code>Point</code> which represents a value offset from the
	 *            <code>ref</code> point oriented based on the nearest line segment.
	 * @return a <code>Point</code> that is the relative coordinate of the label
	 *         that can be used to set it's center location.
	 */
	public static Point relativeCenterCoordinateFromOffset(PointList points, Point ref, Point offset) {
		Vector fromAnchorToLabelCenterPointVector = new Vector(offset.x, offset.y);
		@SuppressWarnings("rawtypes")
		List lineSegments = PointListUtilities.getLineSegments(points);
		LineSeg segmentContainingLabelAnchor = PointListUtilities.getNearestSegment(lineSegments, ref.x, ref.y);
		Vector rotatedVector = getRotatedVector(fromAnchorToLabelCenterPointVector, segmentContainingLabelAnchor, true);

		return new PrecisionPoint(ref.x + rotatedVector.x, ref.y + rotatedVector.y);
	}

	/**
	 * Default constructor.
	 *
	 * @param oldBendPointList
	 *            Bendpoint list before the edge modification
	 * @param newBendPointList
	 *            Bendpoint list after the edge modification
	 * @param isEdgeWithObliqueRoutingStyle
	 *            status of the edge from which to get the previous position of the
	 *            bendpoints and from which to get the three labels
	 * @param oldLabelOffset
	 *            The old offset.
	 * @param labelSize
	 *            The size of the label
	 * @param keyPoint
	 *            The keyPoint of the label (
	 *            {@link org.eclipse.gmf.runtime.diagram.ui.editparts.LabelEditPart#getKeyPoint()}
	 *            )
	 * @param isOnBracketEdge
	 *            True if the parent's label is a bracketEdge (specific locator for
	 *            center label and possibility to rotate the middle segment), false
	 *            otherwise.
	 */
	// @SuppressWarnings("unchecked")
	@SuppressWarnings("unchecked")
	public EdgeLabelQuery(PointList oldBendPointList, PointList newBendPointList, boolean isEdgeWithObliqueRoutingStyle,
			Point oldLabelOffset, Dimension labelSize, Integer keyPoint, boolean isOnBracketEdge) {
		this.isEdgeWithObliqueRoutingStyle = isEdgeWithObliqueRoutingStyle;

		this.oldBendPointList = oldBendPointList;
		this.newBendPointList = newBendPointList;
		this.oldLabelOffset = oldLabelOffset;
		this.labelSize = labelSize;
		this.keyPoint = keyPoint;
		this.isOnBracketEdge = isOnBracketEdge;

		// compute lineSegments from bendPoints
		oldEdgeSegments = PointListUtilities.getLineSegments(oldBendPointList);
		newEdgeSegments = PointListUtilities.getLineSegments(newBendPointList);
	}

	/**
	 * Calculate the new GMF label offset: the label offset defines the position of
	 * the label compared to labelAnchor point. <br>
	 * The new Label offset is computed taking into account:<br>
	 * <ul>
	 * <li>the label anchor point move (start, center or end)</li>
	 * <li>the orientation of the segment owning the label anchor. Indeed, the label
	 * offset is displayed by draw2D relatively to the direction of the edge segment
	 * including the anchor of the label</li>
	 * <li>the expected move of the label according to the functional
	 * specification</li>
	 * </ul>
	 * .
	 *
	 * @return the new offset of the label
	 */
	public Point calculateGMFLabelOffset() {
		if (areBendpointsIdentical() && areSegmentsValid()) {
			return oldLabelOffset;
		} else {
			int anchorPointRatio = getLocation(keyPoint);
			Point oldAnchorPoint = getAnchorPoint(oldBendPointList, anchorPointRatio);
			Point oldLabelCenter = relativeCenterCoordinateFromOffset(oldBendPointList, oldAnchorPoint, oldLabelOffset);

			Point newAnchorPoint = getAnchorPoint(newBendPointList, anchorPointRatio);

			Point newLabelCenter = calculateNewCenterLocation(oldLabelCenter,
					getStandardLabelCenterLocation(newBendPointList, keyPoint));
			return offsetFromRelativeCoordinate(newLabelCenter, newBendPointList, newAnchorPoint);
		}
	}

	private Point getAnchorPoint(PointList pointList, int anchorPointRatio) {
		return PointListUtilities.calculatePointRelativeToLine(pointList, 0, anchorPointRatio, true);
	}

	/**
	 * Check if all segments of new and old points are valid (no segment with same
	 * origin and terminus).
	 *
	 * @return true if segments are valid, false otherwise.
	 */
	private boolean areSegmentsValid() {
		boolean areSegmentsValid = true;
		for (LineSeg lineSeg : newEdgeSegments) {
			if (lineSeg.getOrigin().equals(lineSeg.getTerminus())) {
				areSegmentsValid = false;
				break;
			}
		}

		if (areSegmentsValid) {
			for (LineSeg lineSeg : oldEdgeSegments) {
				if (lineSeg.getOrigin().equals(lineSeg.getTerminus())) {
					areSegmentsValid = false;
					break;
				}
			}
		}
		return areSegmentsValid;
	}

	/**
	 * Check if the old points and the new one are the same.
	 *
	 * @return true if the old points are the same as the new, false otherwise.
	 */
	private boolean areBendpointsIdentical() {
		boolean areBendpointsIdentical = true;
		if (newBendPointList.size() == oldBendPointList.size()) {
			for (int i = 0; i < newBendPointList.size(); i++) {
				Point newPoint = newBendPointList.getPoint(i);
				Point oldPoint = oldBendPointList.getPoint(i);
				if (!newPoint.equals(oldPoint)) {
					areBendpointsIdentical = false;
					break;
				}
			}
		} else {
			areBendpointsIdentical = false;
		}

		return areBendpointsIdentical;
	}

	/**
	 * Calculate the new center location of the label according to functional
	 * specification.
	 *
	 * @param oldCenterLabel
	 *            The old center location of the label.
	 * @param newDefaultLocation
	 *            The standard center location according to the label keyPoint (
	 *            {@link org.eclipse.gmf.runtime.diagram.ui.editparts.LabelEditPart#getKeyPoint()}
	 *            and the default snap back position (
	 *            {@link LabelEditPart#getSnapBackPosition(String)}.
	 * @return
	 */
	private Point calculateNewCenterLocation(Point oldCenterLabel, Point newDefaultLocation) {
		Vector fromOldToNewCenterVector = null;

		// Step 1 : Calculate old reference point (the nearest point on the edge
		// from the center of the label).
		LineSeg oldNearestSeg = PointListUtilities.getNearestSegment(oldEdgeSegments, oldCenterLabel.x,
				oldCenterLabel.y);
		Point oldNearestPoint = oldNearestSeg.perpIntersect(oldCenterLabel.x, oldCenterLabel.y);

		// Step 2 : Is there a new segment and an old segment on the same line?
		// Case of segment increased or decreased (and eventually inverted)
		Optional<Vector> fromOldToNewRefPoint = getVectorFromOldToNewForSegmentsOnSameLine(oldNearestSeg,
				oldNearestPoint, oldCenterLabel);
		if (fromOldToNewRefPoint.isPresent()) {
			// In this case the vector for the reference point is the same than
			// for the label center.
			fromOldToNewCenterVector = fromOldToNewRefPoint.get();
		} else { // No identical segment line has been found
			// RECTILINEAR and TREE routing
			if (!isEdgeWithObliqueRoutingStyle) {
				// Get projection of oldNearestPoint on newSegments along
				// oldRefVector
				LineSeg oldRefVectorIntoSegment = null;
				if (oldCenterLabel.equals(oldNearestPoint)) {
					// Get a segment perpendicular to oldRefSegment going
					// through oldNearestPoint
					oldRefVectorIntoSegment = new LineSeg(oldNearestPoint,
							new PrecisionPoint(
									oldNearestPoint.x + (oldNearestSeg.getOrigin().y - oldNearestSeg.getTerminus().y),
									oldNearestPoint.y - (oldNearestSeg.getOrigin().x - oldNearestSeg.getTerminus().x)));
				} else {
					oldRefVectorIntoSegment = new LineSeg(oldCenterLabel, oldNearestPoint);
				}

				// Is there a new segment at the same index as old segment and
				// with same axis? Case of rectilinear segment move.
				fromOldToNewCenterVector = getVectorForSegmentMoveCase(oldNearestSeg, oldNearestPoint, oldCenterLabel);
				if (fromOldToNewCenterVector == null && isOnBracketEdge) {
					// Is there a new segment at the same index as old segment
					// and with opposite axis? Case of change orientation of the
					// bracket edge
					fromOldToNewCenterVector = getVectorForBracketEdgeOrientationChangeCase(oldNearestSeg,
							oldNearestPoint, oldCenterLabel);
				}
				if (fromOldToNewCenterVector == null) {
					for (LineSeg lineSeg : newEdgeSegments) {
						PointList linesIntersections = oldRefVectorIntoSegment.getLinesIntersections(lineSeg);
						// intersection should be, at more, one point
						if (linesIntersections.size() == 1 && lineSeg.distanceToPoint(linesIntersections.getPoint(0).x,
								linesIntersections.getPoint(0).y) <= Math.sqrt(2)) {
							Vector tempLabelMove = new Vector(linesIntersections.getPoint(0).x - oldNearestPoint.x,
									linesIntersections.getPoint(0).y - oldNearestPoint.y);
							if (fromOldToNewCenterVector == null
									|| tempLabelMove.getLength() < fromOldToNewCenterVector.getLength()) {
								fromOldToNewCenterVector = tempLabelMove;
							}
						}
					}
					// Compare the minimalLabelMove with the default location.
					// If the default location is nearer reset the labelMove.
					Vector fromOldNearestPointToStandardLocation = new Vector(newDefaultLocation.x - oldNearestPoint.x,
							newDefaultLocation.y - oldNearestPoint.y);
					if (fromOldToNewCenterVector == null || fromOldNearestPointToStandardLocation
							.getLength() < fromOldToNewCenterVector.getLength()) {
						fromOldToNewCenterVector = null;
					}
				}
			} else if (newEdgeSegments.size() == oldEdgeSegments.size()) {
				// The newNearestSegment as the same index in
				// newEdgeSegments than oldNearestSegment in oldEdgeSegments
				LineSeg newRefSeg = newEdgeSegments.get(oldEdgeSegments.indexOf(oldNearestSeg));
				// Keep ratio on segment for newRefPoint
				double oldRatio = oldNearestSeg.projection(oldCenterLabel.x, oldCenterLabel.y);
				Point newRefPoint = new PrecisionPoint(
						newRefSeg.getOrigin().x + oldRatio * (newRefSeg.getTerminus().x - newRefSeg.getOrigin().x),
						newRefSeg.getOrigin().y + oldRatio * (newRefSeg.getTerminus().y - newRefSeg.getOrigin().y));
				fromOldToNewCenterVector = new Vector(newRefPoint.x - oldNearestPoint.x,
						newRefPoint.y - oldNearestPoint.y);

			}
		}
		if (fromOldToNewCenterVector == null) {
			return newDefaultLocation;
		} else {
			return oldCenterLabel.getTranslated(fromOldToNewCenterVector.x, fromOldToNewCenterVector.y);
		}
	}

	/**
	 * Check if we are in case of a rectilinear segment move: there is a new segment
	 * at the same index as old nearest segment and with the same axis. Return the
	 * corresponding vector from old to new center in this case, null otherwise.
	 *
	 * @param oldNearestSeg
	 *            The segment that is the nearest from the center of the label in
	 *            the old points list.
	 * @return the corresponding vector from old to new center in case of
	 *         rectilinear segment move, null otherwise.
	 */
	private Vector getVectorForSegmentMoveCase(LineSeg oldNearestSeg, Point oldNearestPoint, Point oldCenterLabel) {
		Vector fromOldToNewCenterVector = null;
		if (newEdgeSegments.size() == oldEdgeSegments.size()) {
			int index = oldEdgeSegments.indexOf(oldNearestSeg);
			LineSeg newNearestSegment = newEdgeSegments.get(index);
			if (oldNearestSeg.isHorizontal() == newNearestSegment.isHorizontal()) {
				Vector oldVector = new Vector(oldNearestSeg.getTerminus().x - oldNearestSeg.getOrigin().x,
						oldNearestSeg.getTerminus().y - oldNearestSeg.getOrigin().y);
				Vector newVector = new Vector(newNearestSegment.getTerminus().x - newNearestSegment.getOrigin().x,
						newNearestSegment.getTerminus().y - newNearestSegment.getOrigin().y);
				fromOldToNewCenterVector = applyOldRatioOnNewSegment(oldNearestSeg, oldNearestPoint, oldCenterLabel,
						newNearestSegment, oldVector.getAngle(newVector) == 180, false);
			}
		}
		return fromOldToNewCenterVector;
	}

	/**
	 * Check if we are in case of a orientation segment change: there is a new
	 * segment at the same index as old nearest segment and with apposite axis.
	 * Return the corresponding vector from old to new center in this case, null
	 * otherwise.
	 *
	 * @param oldNearestSeg
	 *            The segment that is the nearest from the center of the label in
	 *            the old points list.
	 * @return the corresponding vector from old to new center in case of
	 *         orientation segment change, null otherwise.
	 */
	private Vector getVectorForBracketEdgeOrientationChangeCase(LineSeg oldNearestSeg, Point oldNearestPoint,
			Point oldCenterLabel) {
		Vector fromOldToNewCenterVector = null;
		if (newEdgeSegments.size() == oldEdgeSegments.size()) {
			int index = oldEdgeSegments.indexOf(oldNearestSeg);
			LineSeg newNearestSegment = newEdgeSegments.get(index);
			if (oldNearestSeg.isHorizontal() != newNearestSegment.isHorizontal()) {
				Vector oldVector = new Vector(oldNearestSeg.getTerminus().x - oldNearestSeg.getOrigin().x,
						oldNearestSeg.getTerminus().y - oldNearestSeg.getOrigin().y);
				Vector newVector = new Vector(newNearestSegment.getTerminus().x - newNearestSegment.getOrigin().x,
						newNearestSegment.getTerminus().y - newNearestSegment.getOrigin().y);
				double angleInDegree = Math.toDegrees(angleBetween2Lines(oldVector, newVector));
				fromOldToNewCenterVector = applyOldRatioOnNewOrthogonalSegment(oldNearestSeg, oldNearestPoint,
						oldCenterLabel, newNearestSegment, angleInDegree == 90 || angleInDegree == -270);
			}
		}
		return fromOldToNewCenterVector;
	}

	private Optional<Vector> getVectorFromOldToNewForSegmentsOnSameLine(LineSeg oldRefSeg, Point oldRefPoint,
			Point oldCenterLabel) {
		Optional<Vector> result = Optional.empty();
		LineSeg newSegmentOnSameLineWithSameDirection = null;
		LineSeg newSegmentOnSameLineWithOppositeDirection = null;
		// Firstly, for points lists with same nb of segments, search if the
		// vector, at
		// the same index, is on the same line
		int sameLineStatus = NOT_ON_SAME_LINE;
		if (newEdgeSegments.size() == oldEdgeSegments.size()) {
			LineSeg newSegAtSameIndex = newEdgeSegments.get(oldEdgeSegments.indexOf(oldRefSeg));
			sameLineStatus = getSameLineStatus(oldRefSeg, newSegAtSameIndex);
			if (ON_SAME_LINE_SAME_DIRECTION == sameLineStatus) {
				newSegmentOnSameLineWithSameDirection = newSegAtSameIndex;
			} else if (ON_SAME_LINE_OPPOSITE_DIRECTION == sameLineStatus) {
				newSegmentOnSameLineWithOppositeDirection = newSegAtSameIndex;
			}
		}
		if (NOT_ON_SAME_LINE == sameLineStatus) {
			// If this is not the case search on all new segments.
			for (LineSeg newSeg : newEdgeSegments) {
				sameLineStatus = getSameLineStatus(oldRefSeg, newSeg);
				if (ON_SAME_LINE_SAME_DIRECTION == sameLineStatus) {
					newSegmentOnSameLineWithSameDirection = newSeg;
					break;
				} else if (ON_SAME_LINE_OPPOSITE_DIRECTION == sameLineStatus) {
					newSegmentOnSameLineWithOppositeDirection = newSeg;
					// Continue to search a potential segment in the same
					// direction.
				}
			}
		}

		LineSeg newRefSeg = newSegmentOnSameLineWithSameDirection;
		if (newRefSeg == null) {
			newRefSeg = newSegmentOnSameLineWithOppositeDirection;
		}

		if (newRefSeg != null) {
			result = Optional.of(applyOldRatioOnNewSegment(oldRefSeg, oldRefPoint, oldCenterLabel, newRefSeg,
					newSegmentOnSameLineWithOppositeDirection != null, true));
		}
		return result;
	}

	/**
	 * Check if the <code>segment</code> is on the same line as the
	 * <code>referenceSegment</code> and if it is in the same direction or not.
	 *
	 * @param referenceSegment
	 *            The reference segment.
	 * @param segment
	 *            The segment to test
	 * @return one of these statuses {@link #NOT_ON_SAME_LINE},
	 *         {@link #ON_SAME_LINE_SAME_DIRECTION} or
	 *         {@link #ON_SAME_LINE_OPPOSITE_DIRECTION}.
	 */
	private int getSameLineStatus(LineSeg referenceSegment, LineSeg segment) {
		int result = NOT_ON_SAME_LINE;
		if (segment.length() != 0) {
			Vector referenceVector = new Vector(referenceSegment.getTerminus().x - referenceSegment.getOrigin().x,
					referenceSegment.getTerminus().y - referenceSegment.getOrigin().y);
			Vector vector = new Vector(segment.getTerminus().x - segment.getOrigin().x,
					segment.getTerminus().y - segment.getOrigin().y);
			double angle = referenceVector.getAngle(vector);
			if (angle == 0 || angle == 180) {
				Straight straight = new Straight(new PrecisionPoint(segment.getOrigin()),
						new PrecisionPoint(segment.getTerminus()));
				double distToInfiniteLine = straight
						.getDistance(new Vector(referenceSegment.getOrigin().x, referenceSegment.getOrigin().y));
				if (distToInfiniteLine < DISTANCE_TOLERANCE) {
					if (angle == 180) {
						result = ON_SAME_LINE_OPPOSITE_DIRECTION;
					} else {
						result = ON_SAME_LINE_SAME_DIRECTION;
					}
				}
			}
		}
		return result;
	}

	private Vector applyOldRatioOnNewSegment(LineSeg oldRefSeg, Point oldRefPoint, Point oldCenterLabel,
			LineSeg newRefSeg, boolean oppositeDirection, boolean sameLine) {
		Vector result;
		double newRatio = newRefSeg.projection(oldCenterLabel.x, oldCenterLabel.y);
		if (sameLine && newRatio >= 0 && newRatio <= 1) {
			// If the orthogonal projection is inside segment (between 0 and
			// 1), the reference point does not move.
			result = new Vector(0, 0);
		} else {
			Point newRefPoint;
			double oldRatio = oldRefSeg.projection(oldCenterLabel.x, oldCenterLabel.y);
			if (!oppositeDirection) {
				newRefPoint = new PrecisionPoint(
						newRefSeg.getOrigin().x + oldRatio * (newRefSeg.getTerminus().x - newRefSeg.getOrigin().x),
						newRefSeg.getOrigin().y + oldRatio * (newRefSeg.getTerminus().y - newRefSeg.getOrigin().y));
			} else {
				newRefPoint = new PrecisionPoint(
						newRefSeg.getOrigin().x - oldRatio * (newRefSeg.getOrigin().x - newRefSeg.getTerminus().x),
						newRefSeg.getOrigin().y - oldRatio * (newRefSeg.getOrigin().y - newRefSeg.getTerminus().y));
			}
			if (!sameLine && newRatio >= 0 && newRatio <= 1) {
				// If the orthogonal projection is inside segment (between 0 and
				// 1), we keep the oldRefPoint one axis
				if (newRefSeg.isHorizontal()) {
					newRefPoint.setX(oldRefPoint.x);
				} else {
					newRefPoint.setY(oldRefPoint.y);
				}
			}
			Vector vectorFromOldToNewRefPoint = new Vector(newRefPoint.x - oldRefPoint.x,
					newRefPoint.y - oldRefPoint.y);
			if (oldRatio >= 0 && oldRatio <= 1) {
				// Keep ratio on segment for newRefPoint (if it was
				// previously inside segment)
				result = vectorFromOldToNewRefPoint;
			} else {
				// If the label is previously outside of the segment, we
				// keep the shortest point (new or old one).
				Point potentialNewCenter = oldCenterLabel.getTranslated(vectorFromOldToNewRefPoint.x,
						vectorFromOldToNewRefPoint.y);
				if ((newRatio > 1 && newRatio < newRefSeg.projection(potentialNewCenter.x, potentialNewCenter.y))
						|| (newRatio < 0
								&& newRatio > newRefSeg.projection(potentialNewCenter.x, potentialNewCenter.y))) {
					result = new Vector(0, 0);
				} else {
					result = vectorFromOldToNewRefPoint;
				}
			}
		}
		return result;
	}

	private Vector applyOldRatioOnNewOrthogonalSegment(LineSeg oldRefSeg, Point oldRefPoint, Point oldCenterLabel,
			LineSeg newRefSeg, boolean is90Angle) {
		double oldRatio = oldRefSeg.projection(oldCenterLabel.x, oldCenterLabel.y);
		Transform rotateTransform = new Transform();
		// Get the new reference point and the rotation to apply
		Point newRefPoint;
		if (is90Angle) {
			// As GMF coordinates system is reversed (y positive orientation is
			// from top to bottom), we reverse the rotation to apply
			rotateTransform.setRotation(Math.toRadians(-90));
			if (0 <= oldRatio && oldRatio <= 1) {
				// Apply same ratio on the new segment to compute the new
				// reference point
				newRefPoint = new PrecisionPoint(
						newRefSeg.getOrigin().x - oldRatio * (newRefSeg.getOrigin().x - newRefSeg.getTerminus().x),
						newRefSeg.getOrigin().y - oldRatio * (newRefSeg.getOrigin().y - newRefSeg.getTerminus().y));
			} else if (oldRatio > 1) {
				// Just apply the vector from old terminus to old reference
				// point to the new terminus
				Vector vectorFromOldTerminusToOldRefPoint = new Vector(oldRefPoint.x - oldRefSeg.getTerminus().x,
						oldRefPoint.y - oldRefSeg.getTerminus().y);
				Point vectorFromNewTerminusToNewRefPoint = rotateTransform
						.getTransformed(vectorFromOldTerminusToOldRefPoint.toPoint());
				newRefPoint = newRefSeg.getTerminus().getTranslated(vectorFromNewTerminusToNewRefPoint);
			} else {
				Vector vectorFromOldOriginToOldRefPoint = new Vector(oldRefPoint.x - oldRefSeg.getOrigin().x,
						oldRefPoint.y - oldRefSeg.getOrigin().y);
				Point vectorFromNewOriginToNewRefPoint = rotateTransform
						.getTransformed(vectorFromOldOriginToOldRefPoint.toPoint());
				newRefPoint = newRefSeg.getOrigin().getTranslated(vectorFromNewOriginToNewRefPoint);
			}
		} else {
			// As GMF coordinates system is reversed (y positive orientation is
			// from top to bottom), we reverse the rotation to apply
			rotateTransform.setRotation(Math.toRadians(90));
			if (0 <= oldRatio && oldRatio <= 1) {
				// Apply same ratio on the new segment to compute the new
				// reference point (but inverse the origin and the terminus)
				newRefPoint = new PrecisionPoint(
						newRefSeg.getOrigin().x + oldRatio * (newRefSeg.getTerminus().x - newRefSeg.getOrigin().x),
						newRefSeg.getOrigin().y + oldRatio * (newRefSeg.getTerminus().y - newRefSeg.getOrigin().y));
			} else if (oldRatio > 1) {
				// Just apply the vector from old terminus to old reference
				// point to the new terminus
				Vector vectorFromOldTerminusToOldRefPoint = new Vector(oldRefPoint.x - oldRefSeg.getTerminus().x,
						oldRefPoint.y - oldRefSeg.getTerminus().y);
				Point vectorFromNewTerminusToNewRefPoint = rotateTransform
						.getTransformed(vectorFromOldTerminusToOldRefPoint.toPoint());
				newRefPoint = newRefSeg.getTerminus().getTranslated(vectorFromNewTerminusToNewRefPoint);
			} else {
				Vector vectorFromOldOriginToOldRefPoint = new Vector(oldRefPoint.x - oldRefSeg.getOrigin().x,
						oldRefPoint.y - oldRefSeg.getOrigin().y);
				Point vectorFromNewOriginToNewRefPoint = rotateTransform
						.getTransformed(vectorFromOldOriginToOldRefPoint.toPoint());
				newRefPoint = newRefSeg.getOrigin().getTranslated(vectorFromNewOriginToNewRefPoint);
			}
		}
		Vector vectorFromOldRefPointToOldCenterLabel = new Vector(oldCenterLabel.x - oldRefPoint.x,
				oldCenterLabel.y - oldRefPoint.y);
		Point fromNewRefPointToNewCenterLabel = rotateTransform
				.getTransformed(vectorFromOldRefPointToOldCenterLabel.toPoint());
		// Adjust the vector to apply according to orientation change (width
		// becomes height and conversely)
		if (newRefSeg.isHorizontal()) {
			if (0 <= oldRatio && oldRatio <= 1) {
				int invertedWidthHeight = labelSize.width - labelSize.height;
				if (newRefSeg.getOrigin().x > newRefSeg.getTerminus().x) {
					invertedWidthHeight = -invertedWidthHeight;
				}
				fromNewRefPointToNewCenterLabel.translate(0, invertedWidthHeight / 2);
			} else if (oldRatio > 1) {
				int invertedWidthHeight = labelSize.width - labelSize.height;
				if (newRefSeg.getOrigin().x > newRefSeg.getTerminus().x) {
					invertedWidthHeight = -invertedWidthHeight;
				}
				fromNewRefPointToNewCenterLabel.translate(invertedWidthHeight / 2, 0);
			} else {
				fromNewRefPointToNewCenterLabel.translate(0, 0);
				int invertedWidthHeight = labelSize.width - labelSize.height;
				if (newRefSeg.getOrigin().x < newRefSeg.getTerminus().x) {
					invertedWidthHeight = -invertedWidthHeight;
				}
				fromNewRefPointToNewCenterLabel.translate(invertedWidthHeight / 2, 0);
			}
		} else {
			if (0 <= oldRatio && oldRatio <= 1) {
				int invertedWidthHeight = labelSize.width - labelSize.height;
				if (newRefSeg.getOrigin().y > newRefSeg.getTerminus().y) {
					invertedWidthHeight = -invertedWidthHeight;
				}
				fromNewRefPointToNewCenterLabel.translate(invertedWidthHeight / 2, 0);
			} else if (oldRatio > 1) {
				int invertedWidthHeight = labelSize.height - labelSize.width;
				if (newRefSeg.getOrigin().y > newRefSeg.getTerminus().y) {
					invertedWidthHeight = -invertedWidthHeight;
				}
				fromNewRefPointToNewCenterLabel.translate(0, invertedWidthHeight / 2);
			} else {
				int invertedWidthHeight = labelSize.height - labelSize.width;
				if (newRefSeg.getOrigin().y < newRefSeg.getTerminus().y) {
					invertedWidthHeight = -invertedWidthHeight;
				}
				fromNewRefPointToNewCenterLabel.translate(0, invertedWidthHeight / 2);
			}
		}
		// Apply the rotated vector on new reference point to have the new
		// center
		Point newCenterLabel = newRefPoint.getTranslated(fromNewRefPointToNewCenterLabel);
		// Compute the vector from old center to new center
		return new Vector(newCenterLabel.x - oldCenterLabel.x, newCenterLabel.y - oldCenterLabel.y);
	}

	/**
	 * Get the rotated vector according to the segment orientation.
	 *
	 * @param vector
	 *            vector to be rotated
	 * @param segment
	 *            reference segment
	 * @param inverseRotation
	 *            if true, inverse rotation
	 * @return the rotated Vector
	 */
	private static Vector getRotatedVector(Vector vector, LineSeg segment, boolean inverseRotation) {
		Vector result = new Vector(vector.x, vector.y);
		if (vector.x != 0 || vector.y != 0) {
			double angle = angleBetween2Lines(new LineSeg(new Point(0, 0), new Point(1, 0)), segment)
					* (inverseRotation ? -1 : 1);
			Transform rotateTransform = new Transform();
			rotateTransform.setRotation(angle);
			Point rotatedPoint = rotateTransform.getTransformed(vector.toPoint());

			result.x = rotatedPoint.x;
			result.y = rotatedPoint.y;
		}
		return result;
	}

	/**
	 * Get the signed angle between two segments.
	 *
	 * @param line1
	 * @param line2
	 * @return the signed angle in radian.
	 */
	private static double angleBetween2Lines(LineSeg line1, LineSeg line2) {
		if (line1 == null || line2 == null) {
			return 0;
		}
		double angle1 = Math.atan2(line1.getOrigin().y - line1.getTerminus().y,
				line1.getOrigin().x - line1.getTerminus().x);
		double angle2 = Math.atan2(line2.getOrigin().y - line2.getTerminus().y,
				line2.getOrigin().x - line2.getTerminus().x);
		return angle1 - angle2;
	}

	/**
	 * Get the signed angle between two vectors.
	 *
	 * @param vector1
	 *            The first vector
	 * @param vector2
	 *            The second vector
	 * @return the signed angle in radian.
	 */
	private static double angleBetween2Lines(Vector vector1, Vector vector2) {
		if (vector1 == null || vector2 == null) {
			return 0;
		}
		double angle1 = Math.atan2(vector1.y, vector1.x);
		double angle2 = Math.atan2(vector2.y, vector2.x);
		return angle1 - angle2;
	}

	/**
	 * Get the standard center location according to the label keyPoint (
	 * {@link org.eclipse.gmf.runtime.diagram.ui.editparts.LabelEditPart#getKeyPoint()}
	 * ) and the default snap back position (
	 * {@link LabelEditPart#getSnapBackPosition(String)}
	 *
	 * @param pointsList
	 *            The points of the edge of the label.
	 * @param keyPoint
	 *            The keyPoint of the label (
	 *            {@link org.eclipse.gmf.runtime.diagram.ui.editparts.LabelEditPart#getKeyPoint()}
	 *            ).
	 * @return The center of the label {@link Bounds} if this label is located by
	 *         default.
	 */
	private Point getStandardLabelCenterLocation(PointList pointsList, Integer keyPoint) {
		int percentage = getLocation(keyPoint);
		Point newAnchorPoint = getAnchorPoint(pointsList, percentage);
		Point snapBackPosition = getSnapBackPosition(keyPoint);
		Point standardLabelCenter = newAnchorPoint.getTranslated(snapBackPosition);
		return standardLabelCenter;
	}

	/**
	 * Get the location among {@link LabelViewConstants} constants where to relocate
	 * the label figure.
	 *
	 * @return the location among {@link LabelViewConstants} constants
	 */
	private static int getLocation(Integer keyPoint) {
		int location = LabelViewConstants.MIDDLE_LOCATION;
		switch (keyPoint) {
		case ConnectionLocator.SOURCE:
			location = LabelViewConstants.TARGET_LOCATION;
			break;
		case ConnectionLocator.TARGET:
			location = LabelViewConstants.SOURCE_LOCATION;
			break;
		case ConnectionLocator.MIDDLE:
			location = LabelViewConstants.MIDDLE_LOCATION;
			break;
		default:
			location = LabelViewConstants.MIDDLE_LOCATION;
			break;
		}
		return location;
	}
}
